package com.codejoys.monitor.schedule;

import com.codejoys.monitor.Constants;
import com.codejoys.monitor.Utils;
import com.codejoys.monitor.config.SysConfig;
import com.codejoys.monitor.dao.DeviceDao;
import com.codejoys.monitor.domain.ChannelConfig;
import com.codejoys.monitor.domain.Device;
import com.codejoys.monitor.domain.DeviceData;
import com.codejoys.monitor.domain.SysConfigExample;
import com.codejoys.monitor.dto.ChannelConfigMap;
import com.codejoys.monitor.dto.DeviceDto;
import com.codejoys.monitor.event.AlarmEvent;
import com.codejoys.monitor.event.LogStatusEvent;
import com.codejoys.monitor.exception.SysException;
import com.codejoys.monitor.jna.CANLib;
import com.codejoys.monitor.mapper.DeviceDataMapper;
import com.codejoys.monitor.mapper.DeviceMapper;
import com.codejoys.monitor.mapper.SysConfigMapper;
import com.codejoys.monitor.service.impl.ModbusServiceImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.math.BigDecimal;
import java.net.Socket;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

@PropertySource("classpath:application.properties")
@Component
public class SysSchedule {
    private static final Logger logger = LoggerFactory.getLogger(SysSchedule.class);
    private static final Logger statusLogger = LoggerFactory.getLogger("com.codejoys.status.log");

    private static final int MAX_VALUE = 9999990;

    private volatile boolean isDebug = false;

    // 每条记录格式：channel|sn|time|status
    private List<String> statusLogCache = new LinkedList<>();   // 缓存要记录的所有状态响应记录

    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(36);

    @Autowired
    DeviceDao deviceDao;

    @Autowired
    CANLib canLib;

    @Autowired
    SysConfig sysConfig;

    @Autowired
    ChannelConfigMap configMap;

    @Autowired
    DeviceDataMapper deviceDataMapper;

    @Autowired
    DeviceMapper deviceMapper;

    @Autowired
    ApplicationContext applicationContext;

    @Autowired
    SysConfigMapper sysConfigMapper;

    @EventListener
    public void onApplicationEvent(LogStatusEvent event) {
        logger.info("开启状态日志：{}", event.isOn());

        this.isDebug = event.isOn();
    }

    // 发送心跳请求，此任务跑在单独的线程中
    // 见 com.codejoys.monitor.config.ScheduleConfig 配置
    @Scheduled(fixedRateString = "#{sysConfig.moduleHeartbeat}", initialDelay = 500)
    public void deviceHeartbeat() {
        logger.debug("任务执行中。。。");

        // 获取老化中的设备
        final List<DeviceDto> agingDevices = deviceDao.selectAgingDevices();

        // 每个老化中的设备，读取电压电流
        agingDevices.forEach(deviceDto -> {
            Utils.trySleep(20);

            readDeviceData(deviceDto);
        });
    }

    // 记录上次写状态缓存的时间
    private long lastFlush = 0;

    // 读取设备老化状态
    private void readDeviceData(DeviceDto deviceDto) {
        final ChannelConfig config = configMap.getConfigs().get(deviceDto.getChannel());

        int canIndex = config.getCanIndex();
        int moduleId = config.getCanModuleId();

        int status = canLib.GetModuleStatus(canIndex, 0, 1, moduleId);

        // 0： 成功运行   0x3080000：待机   0x2000000:启动中
        boolean powerOff = ((status & 0x2000000) == Constants.POWER_ON_STATE);
        boolean hasError = (Constants.ERROR & status) != 0;

        if (isDebug) {
            statusLogCache.add(config.getChannel()
                    + "|" + deviceDto.getSn()
                    + "|" + Integer.toBinaryString(status)
                    + "|" + System.currentTimeMillis());

            if (lastFlush == 0) {
                lastFlush = System.currentTimeMillis();
            }

            // 每分钟记录一次
            if (lastFlush > 0 && (System.currentTimeMillis() - lastFlush) > 60000) {
                List<String> cache = new LinkedList<>();
                cache.addAll(statusLogCache);
                writeStatusLogs(cache);
                statusLogCache.clear();

                lastFlush = System.currentTimeMillis();
            }
        }

        // 如果设备关机，更新设备状态
        if (powerOff) {
            logger.warn("设备已关机！{}， {}, {}",
                    config.getChannel(),
                    deviceDto.getSn(),
                    Integer.toBinaryString(status));

            // 开机5分钟（可配置）后，读到关机状态码，更新设备状态，避免一直处于老化中
            SysConfigExample example =  new SysConfigExample();
            example.createCriteria().andNameEqualTo(Constants.CONFIG_KEY_ERROR_CHECK_TIME);
            // 查询数据库中的错误检测时间
            List<com.codejoys.monitor.domain.SysConfig> configs = sysConfigMapper.selectByExample(example);

            final int minutes = (configs != null && configs.size() > 0)
                    ? Integer.valueOf(configs.get(0).getValue())
                    : Constants.CONFIG_VALUE_ERROR_CHECK_TIME;          // 默认5分钟

            final long now = new Date().getTime();
            final long start = deviceDto.getAgingStartTime().getTime();

            // 启动时间外，返回关机状态，报错
            if (now - start > minutes * 60 * 1000) {
                deviceDto.setAgingErrorCode(status);

                threadPool.submit(() -> {
                    logger.warn("设备处于关机状态！发送报警！");
                    applicationContext.publishEvent(new AlarmEvent(SysSchedule.this, deviceDto));
                    logDeviceError(deviceDto);
                });
            }
        }

        /*
        if (hasError) {
            logger.warn("设备状态异常{}：{}", config.getChannel(), Integer.toBinaryString(status));

            deviceDto.setAgingErrorCode(status);

            // 老化异常，只需记录设备状态，模块会自动关闭
            threadPool.submit(() -> {
                logger.warn("设备状态异常！发送报警！");
                applicationContext.publishEvent(new AlarmEvent(SysSchedule.this, deviceDto));
                logDeviceError(deviceDto);
            });
        }
        */
    }

    private void writeStatusLogs(List<String> cache) {
        if (cache == null) return;

        StringBuilder sb = new StringBuilder();
        for (String line : cache) {
            sb.append(line).append('\n');
        }

        statusLogger.info(sb.toString());
    }

    // 记录老化异常的设备
    private void logDeviceError(DeviceDto deviceDto) {
        final Device device = deviceMapper.selectByPrimaryKey(deviceDto.getId());

        // 错误次数累计
        if (device.getErrorTimes() == null) {
            deviceDto.setErrorTimes(1);
        } else {
            deviceDto.setErrorTimes(device.getErrorTimes() + 1);
        }

        if (deviceDto.getErrorTimes() >= 5) {
            deviceDto.setStatus((byte) Constants.Status.ERROR.value()); // 老化错误
            deviceDto.setAgingStopTime(new Date());
            deviceDto.setAgingResult((byte) 2);        // 老化异常
        }

        logger.info("更新老化错误的设备信息：{}", deviceDto);
        deviceMapper.updateByPrimaryKeySelective(deviceDto);
    }

    // 定时任务：扫描老化时间到期的设备
    @Scheduled(fixedRateString = "#{sysConfig.checkExpiredDevicesRate}", initialDelay = 10000)
    public void deviceExpireCheckTask() {
        // 查询老化到期的设备
        List<DeviceDto> expiredDevices = deviceDao.selectExpiredDevices();

        expiredDevices.forEach(deviceDto -> {
            onDeviceExpired(deviceDto);
        });
    }

    // 处理过期设备
    public void onDeviceExpired(DeviceDto deviceDto) {
        final ChannelConfig config = configMap.getConfigs().get(deviceDto.getChannel());

        int canIndex = config.getCanIndex();
        int moduleId = config.getCanModuleId();

        int retry = 1;
        int result = 0;
        do {
            // 关闭设备
            result = canLib.TurnOnOffModule(canIndex, 0, (byte) 1, moduleId, 0);

            if (result != 1) {
                logger.error("模块关闭失败！1秒后重试。。{} result: {}", config.getChannel(), result);
                Utils.trySleep(1000);
            } else {
                logger.info("设备{}关闭成功！", config.getChannel());
                break;
            }

            retry++;
            // 最多5次重试
            if (retry > 5) {
                logger.error("5次重试关闭失败，退出。。。");
                break;
            }
        } while (result != 1);

        // 更新设备状态
        deviceDto.setStatus((byte) Constants.Status.FINISHED.value()); // 老化完成
        deviceDto.setAgingStopTime(new Date());
        // 老化完成自动关闭 老化次数才+1
        deviceDto.setAgingTimes(deviceDto.getAgingTimes()+1);
        deviceDto.setAgingResult((byte) 1);        // 老化正常完成
        deviceMapper.updateByPrimaryKeySelective(deviceDto);

        // 等待设备关闭
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 关闭电源
        try (Socket socket = ModbusServiceImpl.connect(config.getCabinetIp())) {
            ModbusServiceImpl.toggleOpen(socket, config, false);
        } catch (IOException e) {
            logger.error("socket连接出错！", e);

            throw new SysException(2003, "socket连接出错！自动停止失败。");
        }

        // 发送移除设备提示
    }

    // 定时任务：采集设备数据（温度、电流、电压）
    @Scheduled(fixedRateString = "#{sysConfig.deviceDataReadRate}", initialDelay = 8000)
    public void deviceDataCollectTask() {
        logger.debug("开始获取设备数据。。。");

        // 获取老化中的设备
        final List<DeviceDto> agingDevices = deviceDao.selectAgingDevices();

        agingDevices.forEach(deviceDto -> {
            fetchDeviceData(deviceDto);
        });
    }

    // 获取一台设备的数据
    public void fetchDeviceData(DeviceDto deviceDto) {
        final ChannelConfig config = configMap.getConfigs().get(deviceDto.getChannel());
        int canIndex = config.getCanIndex();
        int moduleId = config.getCanModuleId();

        try {
            final int voltage = canLib.GetModuleVoltage(canIndex, 0, 1, moduleId);
            // Utils.trySleep(20); // can数据帧需要有时间间隔
            final int current = canLib.GetModuleCurrent(canIndex, 0, 1, moduleId);
            // Utils.trySleep(20);
            final int temperature = canLib.GetModuleTemperature(canIndex, 0, 1, moduleId);
            // Utils.trySleep(20);

            // 数据为0的忽略
            // 电压不为0，电流为0的也记录下来（通道空载的情况） MOD: 20200809
            if ((voltage > 0 && voltage <= MAX_VALUE)   // 温度max：9999.99度
                    // && (current > 0 && current <= MAX_VALUE)
                    && (temperature > 0 && temperature <= MAX_VALUE)) {
                if(current ==0 )
                    logger.warn("当前电流为0！[{}] 电流{}， 电压：{}，温度：{}",
                            config.getChannel(),
                            current,voltage,temperature);
                DeviceData deviceData = new DeviceData();
                deviceData.setCreated(new Date());
                deviceData.setDeviceId(deviceDto.getId());
                deviceData.setTemperature(new BigDecimal(((double) temperature) / 1000));
                deviceData.setCurrent(new BigDecimal(((double) current) / 1000));
                deviceData.setVoltage(new BigDecimal(((double) voltage) / 1000));
                deviceDataMapper.insertSelective(deviceData);
            } else {
                logger.warn("读取模块数据异常！[{}] 电流{}， 电压：{}，温度：{}",
                        config.getChannel(),
                        current, voltage, temperature);
            }
        } catch (Exception e) {
            logger.error("获取模块数据异常！", e);
        }
    }
}
